"use strict";
// 人脸扫光效果 - 优化：减少每帧分配并增强扫光可见性
var canvas = null;
var gl = null;
var image = null;

// GL resources cached
var program = null;
var positionBuffer = null;
var texcoordBuffer = null;
var texture = null;

// attribute/uniform locations
var positionLocation = null;
var texcoordLocation = null;
var resolutionLocation = null;
var scanYLocation = null;
var scanWidthLocation = null;
var scanColorLocation = null;
var scanIntensityLocation = null;

// animation state
var scanY = 0.0;
var cycle = 180; // frames for a full pass (lower -> faster)

// parameters to make the effect more obvious
var DEFAULT_SCAN_WIDTH = 0.06; // 6% of texture height
var DEFAULT_SCAN_COLOR = [0.9, 0.95, 1.0]; // light bluish-white
var DEFAULT_SCAN_INTENSITY = 1.8; // boost (additive-like)
var prevWidth = 0;
var prevHeight = 0;
var positionArray = null;

export const main = function (canvasEL) {
  canvas = canvasEL;
  image = new Image();
  image.src = "../background.png"; // MUST BE SAME DOMAIN!!!
  image.onload = function () {
    initGL();
    requestAnimationFrame(render);
  };
};

function initGL() {
  if (!canvas) return;
  gl = canvas.getContext("webgl");
  if (!gl) return;

  // vertex shader
  var vertexShaderSource = `
    attribute vec2 a_position;
    attribute vec2 a_texCoord;
    uniform vec2 u_resolution;

    varying vec2 v_texCoord;

    void main() {
      vec2 zeroToOne = a_position / u_resolution;
      vec2 zeroToTwo = zeroToOne * 2.0;
      vec2 clipSpace = zeroToTwo - 1.0;
      gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
      v_texCoord = a_texCoord;
    }
  `;

  // fragment shader: smooth scanline with configurable width/intensity
  var fragmentShaderSource = `
    precision mediump float;
    uniform sampler2D u_image;
    varying vec2 v_texCoord;
    uniform float u_scanY;
    uniform float u_scanWidth;
    uniform vec3 u_scanColor;
    uniform float u_scanIntensity;
    // color range detection (HSV)
    uniform float u_hueMin;
    uniform float u_hueMax;
    uniform float u_satMin;
    uniform float u_satMax;
    uniform float u_valMin;
    uniform float u_valMax;

    void main() {
      vec4 color = texture2D(u_image, v_texCoord);
      float dist = abs(v_texCoord.y - u_scanY);
      float t = 1.0 - smoothstep(0.0, u_scanWidth, dist);

      // convert rgb -> hsv (h in 0..1)
      vec3 c = color.rgb;
      float mx = max(max(c.r, c.g), c.b);
      float mn = min(min(c.r, c.g), c.b);
      float d = mx - mn;
      float h = 0.0;
      if (d > 0.00001) {
        if (mx == c.r) {
          h = mod((c.g - c.b) / d, 6.0) / 6.0;
        } else if (mx == c.g) {
          h = ((c.b - c.r) / d + 2.0) / 6.0;
        } else {
          h = ((c.r - c.g) / d + 4.0) / 6.0;
        }
      }
      float s = (mx == 0.0) ? 0.0 : d / mx;
      float v = mx;

      // hue wrap handling (hue ranges may wrap around 1.0)
      float hueMask = 0.0;
      if (u_hueMin <= u_hueMax) {
        hueMask = smoothstep(u_hueMin, u_hueMax, h);
      } else {
        // wrapped range: e.g. min=0.95 max=0.05
        hueMask = max(smoothstep(u_hueMin, 1.0, h), smoothstep(0.0, u_hueMax, h));
      }

      float satMask = smoothstep(u_satMin, u_satMax, s);
      float valMask = smoothstep(u_valMin, u_valMax, v);

      // combined mask for color region (soft)
      float colorMask = hueMask * satMask * valMask;

      // final mask includes scanline t
      float mask = colorMask * t;

      vec3 outColor = mix(color.rgb, clamp(color.rgb + u_scanColor * u_scanIntensity, 0.0, 1.0), mask);
      gl_FragColor = vec4(outColor, color.a);
    }
  `;

  var vs = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
  var fs = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
  program = createProgram(gl, vs, fs);

  // look up locations once
  positionLocation = gl.getAttribLocation(program, "a_position");
  texcoordLocation = gl.getAttribLocation(program, "a_texCoord");
  resolutionLocation = gl.getUniformLocation(program, "u_resolution");
  scanYLocation = gl.getUniformLocation(program, "u_scanY");
  scanWidthLocation = gl.getUniformLocation(program, "u_scanWidth");
  scanColorLocation = gl.getUniformLocation(program, "u_scanColor");
  scanIntensityLocation = gl.getUniformLocation(program, "u_scanIntensity");
  var imageLocation = gl.getUniformLocation(program, "u_image");
  var hueMinLocation = gl.getUniformLocation(program, "u_hueMin");
  var hueMaxLocation = gl.getUniformLocation(program, "u_hueMax");
  var satMinLocation = gl.getUniformLocation(program, "u_satMin");
  var satMaxLocation = gl.getUniformLocation(program, "u_satMax");
  var valMinLocation = gl.getUniformLocation(program, "u_valMin");
  var valMaxLocation = gl.getUniformLocation(program, "u_valMax");

  // create buffers once and upload static texcoords
  positionBuffer = gl.createBuffer();
  texcoordBuffer = gl.createBuffer();

  gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
  // we can reuse a single small typed array
  gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array([0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1]),
    gl.STATIC_DRAW
  );

  // texture
  texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
  // upload image once
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

  // set sampler uniform to texture unit 0
  gl.useProgram(program);
  gl.uniform1i(imageLocation, 0);

  // default HSV ranges for skin-like colors (tunable)
  // hue in [0,1], e.g., 0.0 - 0.12 ~ 0-43deg
  gl.uniform1f(hueMinLocation, 0.0);
  gl.uniform1f(hueMaxLocation, 0.12);
  gl.uniform1f(satMinLocation, 0.15);
  gl.uniform1f(satMaxLocation, 0.9);
  gl.uniform1f(valMinLocation, 0.15);
  gl.uniform1f(valMaxLocation, 1.0);

  // small GL state
  gl.enable(gl.BLEND);
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

  // prepare initial position array (uploaded lazily when render runs)
  prevWidth = 0;
  prevHeight = 0;
}

function render() {
  if (!gl) return;

  // update canvas/viewport in case size changed
  if (gl.canvas.width !== image.width || gl.canvas.height !== image.height) {
    gl.canvas.width = image.width;
    gl.canvas.height = image.height;
  }
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  gl.clearColor(0, 0, 0, 0);
  gl.clear(gl.COLOR_BUFFER_BIT);

  gl.useProgram(program);

  // update position buffer only if size changed (avoid per-frame allocations)
  if (prevWidth !== image.width || prevHeight !== image.height) {
    prevWidth = image.width;
    prevHeight = image.height;
    var x1 = 0;
    var y1 = 0;
    var x2 = image.width;
    var y2 = image.height;
    positionArray = new Float32Array([x1, y1, x2, y1, x1, y2, x1, y2, x2, y1, x2, y2]);
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, positionArray, gl.STATIC_DRAW);
  } else {
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  }

  // set attributes
  gl.enableVertexAttribArray(positionLocation);
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

  gl.enableVertexAttribArray(texcoordLocation);
  gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
  gl.vertexAttribPointer(texcoordLocation, 2, gl.FLOAT, false, 0, 0);

  // uniforms
  gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);

  // set scan and visual uniforms (scan color/intensity already set once but keep updating scanY)

  // animate scanline
  scanY += 1.0 / cycle;
  if (scanY > 1.0) scanY = 0.0;
  gl.uniform1f(scanYLocation, scanY);
  gl.uniform1f(scanWidthLocation, DEFAULT_SCAN_WIDTH);
  gl.uniform3fv(scanColorLocation, DEFAULT_SCAN_COLOR);
  gl.uniform1f(scanIntensityLocation, DEFAULT_SCAN_INTENSITY);

  // bind texture unit 0
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, texture);

  gl.drawArrays(gl.TRIANGLES, 0, 6);

  requestAnimationFrame(render);
}

function createShader(gl, type, source) {
  var shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (success) return shader;
  var info = gl.getShaderInfoLog(shader);
  gl.deleteShader(shader);
  console.error('Shader compile failed:', info);
}

function createProgram(gl, vertexShader, fragmentShader) {
  var program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  var success = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (success) return program;
  var info = gl.getProgramInfoLog(program);
  gl.deleteProgram(program);
  console.error('Program link failed:', info);
}
